﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Windows.Threading;
using System.Collections;
using System.Collections.Specialized;

namespace ViewModel
{
    /// <summary>
    /// A thread aware version of typed ObservableCollection
    /// enabling CollectionChanged events to be dispatched on the UI thread
    /// </summary>
    /// <typeparam name="T">The type of this collection</typeparam>
    public class ThreadedObservableCollection<T> : ObservableCollection<T>
    {
        /// <summary>
        /// Overriden event so that this class can access it
        /// </summary>
        public override event NotifyCollectionChangedEventHandler CollectionChanged;

        /// <summary>
        /// Construct an instance using a typed IList
        /// </summary>
        /// <param name="list"><see cref="IList"/> to construct the collection with. Calls the base class.</param>
        public ThreadedObservableCollection(IList<T> list) : base(list) { }

        /// <summary>
        /// Construct an instance using a typed IEnumerable
        /// </summary>
        /// <param name="list"><see cref="IEnumerable"/> to construct the collection with. Calls the base class.</param>
        public ThreadedObservableCollection(IEnumerable<T> list) : base(list) { }


        /// <summary>
        /// Overriden OnCollectionChanged method for dispatching on UI thread
        /// </summary>
        /// <param name="e">Arguments</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            // Be nice - use BlockReentrancy like MSDN said
            using (BlockReentrancy())
            {
                NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;
                if (eventHandler == null)
                    return;

                Delegate[] delegates = eventHandler.GetInvocationList();
                // Walk thru invocation list
                foreach (NotifyCollectionChangedEventHandler handler in delegates)
                {
                    DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
                    // If the subscriber is a DispatcherObject and different thread
                    if (dispatcherObject != null && dispatcherObject.CheckAccess() == false)
                    {
                        // Invoke handler in the target dispatcher's thread
                        dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);
                    }
                    else // Execute handler as is
                    {
                        handler(this, e);
                    }
                }
            }
        }
    }
}
